/*
Authors: Eli Yucht 300388949 and Lior Fass 300069705
Project: Hamming
Description: Hamming Encoder Decoder Class implementation
*/
#include "Hamming_EDM.h"

/*
* This BITMAP holds the flags of the bits participating in each parity bit calculation
* there are 5 lines one for each Parity bit of haming (31,26)
* there are 26 bits in each line (implemented as 4 bytes)
*/
const unsigned char Hamming_EDM::hamming_MAP[5][4] =
	{
		{0xDA, 0xB5, 0x55, 0x40},
		{ 0xB6, 0x6c, 0xCC, 0xC0 },
		{ 0x71, 0xE3, 0xC3, 0xC0 },
		{ 0x0F, 0xE0, 0x3F, 0xC0 },
		{ 0x00, 0x1F, 0xFF, 0xC0 }
	};

/*
* encode 26 bits(assumed 4 bytes) with haming code and adds the 5 parity bits calculated in the end.
*
* Input:
* -----
* hamming_blocks - pointer to array of 4 bytes holding 26 bits(and 6 unused bits)
*
* Output:
* -----
* 5 parity bits are added to the 4th byte(after the 26 info bits)
* the bits are added in opposide direction for convinience "i1 i2 i3... i26 p5 p4 p3 p2 p1 X"
*/
void Hamming_EDM::encode_26To31(unsigned char * hamming_blocks)
{
	unsigned char res;

	res = calc_parity(hamming_blocks);

	hamming_blocks[3] |= (res << 1); //add the result as bit 27 to 31
}

/*
* encode 4 or 8 blocks of 26 bits each, using encode_26To31() function 
*
* Input:
* -----
* num_of_blocks - number of blocks to be encoded
* hamming_blocks - (pointer to array of 4 bytes holding 26 bits) X num_of_blocks
*
* Output:
* -----
* each blocks return with the 5 extra parity bits 
*/
void Hamming_EDM::encode_Blocks(unsigned char * hamming_blocks, int num_of_blocks)
{
	if ((num_of_blocks != 4) & (num_of_blocks != 8))
	{
		cout << "only 4 or 8 blocks supported" << endl;
		return;
	}

	for (int i = 0; i < num_of_blocks; i++)
		encode_26To31(&hamming_blocks[i * 4]);
}

/*
* implementation of the Hamming Encoder - for each parity bit the function masks the relevant bits and then XOR them(using count 1s and modulus 2)
*
* Input:
* -----
* hamming_blocks - pointer to array of 4 bytes holding 26 bits
*
* Output:
* -----
* res - 1 byte holding the 5 parity bits calculated
*/
unsigned char Hamming_EDM::calc_parity(unsigned char * hamming_blocks)
{
	int parity;
	int i, j;
	unsigned char res = 0;

	for (i = 0; i <= 4; i++) //5 Parity bits
	{
		parity = 0;
		for (j = 0; j <= 3; j++) //4 Bytes for the 26 bits
			parity += count1s(hamming_blocks[j] & hamming_MAP[i][j]);
		res ^= ((parity % 2) << i); //modulus 2, put in right place
	}

	return res;
}


/*
* implementation of the Hamming Decoder - this function gets  bits block, encode the info bits(first 26 bits),
* compare them to the parity bits recieved(XOR), to get the index of the problematic bit.
* if all parity bits are 0 then no errors in the block
*
* Input:
* -----
* hamming_blocks - pointer to array of 4 bytes holding 31 bits
*
* Output:
* -----
* int holding the number of corrections (0 or 1)
* the correction is being processed on the block memory space
*/
int Hamming_EDM::decode_31To26(unsigned char * hamming_blocks)
{
	unsigned char res,err;
	int bit_num, NumOfCorrected;

	res = calc_parity(hamming_blocks);

	err = ((hamming_blocks[3] & 0x3e) >> 1) ^ res;

	//cout << "error in hamming bit " << +err << endl;
	if (err != 0x00)
	{
		bit_num = calc_bit_num(err);
		if (bit_num > 0)
		{
			//cout << "should correct bit " << bit_num << endl;
			correct_bit(bit_num, hamming_blocks);
			NumOfCorrected = 1;
		}
		else
			cout << "error in bit num calculation" << endl;
	}
	else
		NumOfCorrected = 0;
	
	return NumOfCorrected;
}

/*
* function that count the number of 1's in a Byte. based on "stack Overflow" comment
*
* Input:
* -----
* byte_in - Byte - 8 bits
*
* Output:
* -----
* c - int - number of 1s in byte_in (0-8)
*/
int Hamming_EDM::count1s(unsigned char byte_in)
{
	unsigned int c; // c accumulates the total bits set in byte_in
	for (c = 0; byte_in; c++)
	{
		byte_in &= byte_in - 1; // clear the least significant bit set
	}
	return c;
}

/*
* translate the Hamming address of wrong bit, to an index in our block of 31 bits
*
* Input:
* -----
* err - byte holding 5 relevant bits represanting the place of the error in hamming encoding 
*
* Output:
* -----
* int - the index of the bit in our block
*/
int Hamming_EDM::calc_bit_num(unsigned char err)
{
	if (err < 3)
		return (32 - err);
	else if (err == 3)
		return err - 2;
	else if (err == 4)
		return 29;
	else if (err < 8)
		return err - 3;
	else if (err == 8)
		return 28;
	else if (err < 16)
		return err - 4;
	else if (err == 16)
		return 27;
	else if (err < 32)
		return err - 5;
	else
		return -1; //error shouldn't get here
}

/*
* flip one bit in our 31 bits block
*
* Input:
* -----
* bit_num - index of the bit that should be flipped calculated by calc_bit_num
* hamming_blocks - pointer to the block start
*
* Output:
* -----
* the bit is being flipped in the block
*/
void Hamming_EDM::correct_bit(int bit_num, unsigned char * hamming_blocks)
{
	bit_num = bit_num - 1; //change to zero base
	int byte_num = bit_num / 8;
	int bit_in_byte = bit_num % 8;
	hamming_blocks[byte_num] ^= ((unsigned char)0x80 >> (bit_in_byte));
}

/*
* decode 4 or 8 blocks of 31 bits each, using decode_31To26() function
*
* Input:
* -----
* num_of_blocks - number of blocks to be encoded
* hamming_blocks - (pointer to array of 4 bytes holding 31 bits) X num_of_blocks
*
* Output:
* -----
* each blocks return after fixing errors according to its 5 parity bits
*/
int Hamming_EDM::decode_Blocks(unsigned char * hamming_blocks, int num_of_blocks)
{
	if ((num_of_blocks != 4) & (num_of_blocks != 8))
	{
		cout << "only 4 or 8 blocks supported" << endl;
		return -1;
	}

	int numOfCorrected = 0;

	for (int i = 0; i < num_of_blocks; i++)
		numOfCorrected = numOfCorrected + decode_31To26(&hamming_blocks[i * 4]);

	return numOfCorrected;
}